package com.tenement.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.mybatisflex.core.paginate.Page;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.core.tenant.TenantManager;
import com.mybatisflex.core.update.UpdateChain;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.tenement.common.GlobalException;
import com.tenement.common.RedisConConstant;
import com.tenement.dto.LoginUserDto;
import com.tenement.dto.SelectDto;
import com.tenement.entity.HouseUser;
import com.tenement.entity.User;
import com.tenement.entity.UserTenement;
import com.tenement.enums.RespBusiness;
import com.tenement.mapper.UserMapper;
import com.tenement.service.*;
import com.tenement.utils.JwtUtil;
import com.tenement.utils.RedisCache;
import com.tenement.vo.QueryVo;
import com.tenement.vo.UserRoleVo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.tenement.entity.table.DeptTableDef.DEPT;
import static com.tenement.entity.table.PostTableDef.POST;
import static com.tenement.entity.table.PropertiesTableDef.PROPERTIES;
import static com.tenement.entity.table.UserTableDef.USER;


/**
 *  服务层实现。
 *
 * @author BertXie
 * @since 1.0.1
 */
@Service
@RequiredArgsConstructor
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private final UserMapper userMapper;

    private final UserTenementService userTenementService;

    private final BCryptPasswordEncoder bCryptPasswordEncoder;

    private final AuthenticationManager authenticationManager;

    private final RedisCache redisCache;

    private final UserRoleService userRoleService;

    private final HouseUserService houseUserService;

    private final RepairService repairService;

    @Autowired
    @Lazy
    private ChatService chatService;

    @Override
    public Map<String,Object> login(String account, String password) {
        UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(account, password);
        Authentication authenticate = authenticationManager.authenticate(authToken);
        if (authenticate == null) {
            throw new GlobalException(RespBusiness.PASSWORD_MISMATCH);
        }
        LoginUserDto loginUserDto = (LoginUserDto) authenticate.getPrincipal();
        String jwt = JwtUtil.createJWT(loginUserDto.getUser().getId().toString());
        Map<String, Object> res = new HashMap<>();
        res.put("token", jwt);
        res.put("user", loginUserDto);
        redisCache.setValue(RedisConConstant.USER_LOGIN_TOKEN + loginUserDto.getUser().getId().toString(), loginUserDto);
        return res;
    }

    @Override
    public Map<String, Object> miniLogin(String account) {
        User user = TenantManager.withoutTenantCondition(() -> byAccount(account));
        if (ObjectUtil.isNull(user)) {
            throw new GlobalException(RespBusiness.NO_USER_EXISTS);
        }
        LoginUserDto loginUserDto = new LoginUserDto();
        String jwt = JwtUtil.createJWT(user.getId().toString());
        loginUserDto.setUser(user);
        UsernamePasswordAuthenticationToken authenticationToken =
                new UsernamePasswordAuthenticationToken(loginUserDto, null, null);
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
        Map<String, Object> res = new HashMap<>();
        res.put("token", jwt);
        res.put("user", loginUserDto);
        redisCache.setValue(RedisConConstant.USER_LOGIN_TOKEN + user.getId().toString(), loginUserDto);
        return res;
    }

    @Override
    public boolean loginOut(HttpServletRequest req) {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (ObjectUtil.isNotNull(authentication)) {
            log.info("当前用户："+authentication.getPrincipal());
            LoginUserDto principal = (LoginUserDto) authentication.getPrincipal();
            return redisCache.delete(RedisConConstant.USER_LOGIN_TOKEN + principal.getUser().getId());
        }
        throw new GlobalException(RespBusiness.EXPIRED_LOGIN);
    }

    @Transactional
    @Override
    public int workerOperator(User user) {
        if(user.getId() == null && user.getPId() != null) {
            User existUser = TenantManager.withoutTenantCondition(() -> this.byAccount(user.getPhoneNumber()));
            if (ObjectUtil.isNotNull(existUser)) { throw new GlobalException(RespBusiness.USER_EXISTS); }
            user.setPasswordHash(bCryptPasswordEncoder.encode(user.getPhoneNumber().substring(5)));
            user.setType(1);
            List<Long> roleIds = userRoleService.roleIds(user.getPId());
            userMapper.insertSelective(user);
            bindProperty(new UserTenement().setUserId(user.getId()).setTenementId(user.getPropertyId()));
            return userRoleService.operator(new UserRoleVo().setUserId(user.getId()).setRoleIds(roleIds)) ? 1 : 0;
        }else {
            return userMapper.update(user);
        }
    }


    @Override
    public int add(User user){
        User existUser = TenantManager.withoutTenantCondition(() -> this.byAccount(user.getPhoneNumber()));
        if (ObjectUtil.isNotNull(existUser)) { throw new GlobalException(RespBusiness.USER_EXISTS); }
        user.setType(0);
        user.setPasswordHash(bCryptPasswordEncoder.encode(user.getPhoneNumber().substring(5)));
        return TenantManager.withoutTenantCondition(() -> userMapper.insertSelective(user));
    }



    @Override
    public boolean banUser(String account) {
        User user = byAccount(account);
        if (ObjectUtil.isNotNull(user)) {
            return UpdateChain.of(User.class)
                    .set(USER.IS_ACTIVE, user.getIsActive() == 0 ? 1 : 0)
                    .where(USER.EMAIL.eq(account)
                            .or(USER.PHONE_NUMBER.eq(account).or(USER.ACCOUNT.eq(account)))
                    ).update();
        }
        return false;
    }

    @Override
    public Page<User> list(QueryVo queryVo) {
       return TenantManager.withoutTenantCondition(() -> {
            QueryWrapper queryWrapper = QueryWrapper.create()
                    .from(USER)
                    .select(
                            USER.ID, USER.ACCOUNT,
                            USER.EMAIL, USER.PHONE_NUMBER, USER.NAME,
                            USER.PROPERTY_ID, USER.CREATE_TIME, USER.UPDATE_TIME,
                            USER.PROFILE_PICTURE, USER.PASSWORD_HASH,
                            USER.IS_ACTIVE, USER.POSITION_ID,
                            DEPT.ID, DEPT.NAME, DEPT.LEADER, DEPT.EMAIL,
                            DEPT.PHONE, DEPT.P_ID,
                            DEPT.SORT_ORDER, DEPT.CREATE_TIME,
                            DEPT.UPDATE_TIME, DEPT.ANCESTORS,
                            POST.ID, POST.NAME, POST.CODE,
                            POST.SORT_ORDER, POST.STATUS,
                            POST.CREATE_TIME, POST.UPDATE_TIME, POST.REMARK,
                            PROPERTIES.ID, PROPERTIES.COMMUNITY_NAME, PROPERTIES.ADDRESS,
                            PROPERTIES.PROPERTY_NAME, PROPERTIES.PROPERTY_TYPE, PROPERTIES.PROPERTY_FEE,
                            PROPERTIES.LATITUDE, PROPERTIES.LONGITUDE, PROPERTIES.CONTACT_PERSON,
                            PROPERTIES.WATER_FEE, PROPERTIES.GAS_FEE, PROPERTIES.ELECTRICITY_FEE
                    )
                    .leftJoin(DEPT).on(DEPT.ID.eq(USER.DEPARTMENT_ID))
                    .leftJoin(POST).on(POST.ID.eq(USER.POSITION_ID))
                    .leftJoin(PROPERTIES).on(PROPERTIES.ID.eq(USER.PROPERTY_ID))
                    .where(
                            USER.REAL_NAME
                                    .like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord()))
                                    .or(USER.NAME.like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord())))
                                    .or(USER.PHONE_NUMBER.like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord())))
                                    .or(PROPERTIES.PROPERTY_NAME.like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord())))
                    )
                    .and(USER.IS_ACTIVE.eq(queryVo.getStatus(), queryVo.getStatus() != -1))
                    .orderBy(USER.CREATE_TIME.desc());
            return userMapper.paginateAs(queryVo.getPage(), queryVo.getSize(), queryWrapper, User.class);
        });
    }


    @Override
    public User byAccount(String account) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .from(USER)
                .select(
                        USER.ID, USER.ACCOUNT,
                        USER.EMAIL, USER.PHONE_NUMBER, USER.NAME,USER.REAL_NAME,
                        USER.PROPERTY_ID, USER.CREATE_TIME, USER.UPDATE_TIME,
                        USER.PROFILE_PICTURE, USER.PASSWORD_HASH,
                        USER.IS_ACTIVE, USER.POSITION_ID,
                        USER.ID_CARD,USER.EMERGENCY_CONTACT_NAME,USER.EMERGENCY_CONTACT_PHONE_NUMBER,
                        USER.GRADUATED_FROM,USER.EDUCATION,USER.MAJOR,USER.JOIN_DATE,USER.CONTRACT_PERIOD,
                        USER.CONTRACT_EXPIRY_DATE,USER.SEX,USER.TYPE,
                        DEPT.ID,DEPT.NAME,DEPT.LEADER,DEPT.EMAIL,
                        DEPT.PHONE,DEPT.P_ID,
                        DEPT.SORT_ORDER, DEPT.CREATE_TIME,
                        DEPT.UPDATE_TIME,DEPT.ANCESTORS,
                        POST.ID,POST.NAME,POST.CODE,
                        POST.SORT_ORDER,POST.STATUS,
                        POST.CREATE_TIME,POST.UPDATE_TIME,POST.REMARK,
                        PROPERTIES.ID, PROPERTIES.PROPERTY_NAME, PROPERTIES.PROPERTY_TYPE,
                        PROPERTIES.ADDRESS,PROPERTIES.LONGITUDE,PROPERTIES.LATITUDE,
                        PROPERTIES.BUILDING_AREA,PROPERTIES.PROPERTY_FEE,PROPERTIES.WATER_FEE,PROPERTIES.GAS_FEE,PROPERTIES.ELECTRICITY_FEE,
                        PROPERTIES.COMMUNITY_NAME,PROPERTIES.COMMUNITY_DESCRIPTION,PROPERTIES.CONTACT_PERSON,PROPERTIES.PHONE_NUMBER,
                        PROPERTIES.EMAIL,PROPERTIES.REGION,PROPERTIES.ESTABLISHED_DATE
                )
                .leftJoin(DEPT).on(DEPT.ID.eq(USER.DEPARTMENT_ID))
                .leftJoin(POST).on(POST.ID.eq(USER.POSITION_ID))
                .leftJoin(PROPERTIES).on(PROPERTIES.ID.eq(USER.PROPERTY_ID))
                .where(USER.EMAIL.eq(account).or(USER.PHONE_NUMBER.eq(account).or(USER.ACCOUNT.eq(account))));
        return userMapper.selectOneByQueryAs(queryWrapper,User.class);
    }

    @Override
    public List<SelectDto> adminBindSelect(String keyWord) {
        return userMapper.adminBindSelect(keyWord);
    }

    @Override
    public boolean bindProperty(UserTenement userTenement) {
        boolean status = userMapper.bindProperty(userTenement);
        if (status) {
            userTenementService.operator(userTenement);
        }
        return status;
    }

    @Override
    public Page<User> worker(QueryVo queryVo) {
        QueryWrapper queryWrapper = QueryWrapper.create()
                .from(USER)
                .select(
                        USER.ID, USER.ACCOUNT,
                        USER.EMAIL, USER.PHONE_NUMBER, USER.NAME,
                        USER.PROPERTY_ID, USER.CREATE_TIME, USER.UPDATE_TIME,
                        USER.PROFILE_PICTURE, USER.IS_ACTIVE, USER.POSITION_ID,USER.DEPARTMENT_ID,
                        USER.ID_CARD,USER.CONTRACT_EXPIRY_DATE,USER.CONTRACT_PERIOD,USER.JOIN_DATE,
                        USER.REAL_NAME,USER.EDUCATION,USER.GRADUATED_FROM,USER.SEX,USER.MAJOR,
                        USER.EMERGENCY_CONTACT_NAME,USER.EMERGENCY_CONTACT_PHONE_NUMBER,
                        DEPT.ID, DEPT.NAME, DEPT.LEADER, DEPT.EMAIL,
                        DEPT.PHONE, DEPT.P_ID,
                        DEPT.SORT_ORDER, DEPT.CREATE_TIME,
                        DEPT.UPDATE_TIME, DEPT.ANCESTORS,
                        POST.ID, POST.NAME, POST.CODE,
                        POST.SORT_ORDER, POST.STATUS,
                        POST.CREATE_TIME, POST.UPDATE_TIME, POST.REMARK
                )
                .leftJoin(DEPT).on(DEPT.ID.eq(USER.DEPARTMENT_ID))
                .leftJoin(POST).on(POST.ID.eq(USER.POSITION_ID))
                .where(
                        USER.REAL_NAME
                                .like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord()))
                                .or(USER.NAME.like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord())))
                                .or(USER.PHONE_NUMBER.like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord())))
                                .or(POST.NAME.like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord())))
                                .or(DEPT.LEADER.like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord())))
                                .or(POST.REMARK.like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord())))
                                .or(DEPT.NAME.like(queryVo.getKeyWord(), StrUtil.isNotEmpty(queryVo.getKeyWord())))
                )
                .and(USER.POSITION_ID.isNotNull().or(USER.DEPARTMENT_ID.isNotNull()))
                .and(USER.IS_ACTIVE.eq(queryVo.getStatus(), queryVo.getStatus() != -1))
                .and(DEPT.ID.eq(queryVo.getDeptId(), queryVo.getDeptId() != -1L).or(DEPT.P_ID.eq(queryVo.getDeptId(), queryVo.getDeptId() != -1L)))
                .orderBy(USER.CREATE_TIME.desc());
        return userMapper.paginateAs(queryVo.getPage(), queryVo.getSize(), queryWrapper, User.class);
    }

    @Override
    public int removeWorker(List<Long> ids) {
        userRoleService.removeByUserId(ids);
        return userMapper.deleteBatchByIds(ids);
    }

    @Transactional
    @Override
    public int residentOperate(Long houseId,User user) {
        if (houseId == null) {
            return upDateResident(user) ? 1 : 0;
        }else {
            User dbUser = byAccount(user.getPhoneNumber());
            int rows = 0;
            if (ObjectUtil.isNotNull(dbUser)) {
                user.setId(dbUser.getId());
                int upDateResident = upDateResident(user) ? 1 : 0;
                int houseUserRows = houseUserService.add(new HouseUser().setUserId(dbUser.getId()).setHouseId(houseId).setStatus((short) 1).setRemark("物业管理员添加"));
                int tenementRows = userTenementService.operator(new UserTenement(dbUser.getId(), user.getPropertyId()));
                if (houseUserRows > 0 || tenementRows > 0 || upDateResident > 0) {
                    rows = 1;
                }
            }else {
                user.setPasswordHash(bCryptPasswordEncoder.encode(user.getPhoneNumber().substring(5)));
                rows = userMapper.insertSelective(user);
                int houseUserRows = houseUserService.add(new HouseUser().setUserId(user.getId()).setHouseId(houseId).setStatus((short) 1).setRemark("物业管理员添加"));
                int tenementRows = userTenementService.operator(new UserTenement(user.getId(), user.getPropertyId()));
                if (houseUserRows > 0 || tenementRows > 0 || rows > 0) {
                    rows = 1;
                }
            }
            return rows;
        }
    }

    @Override
    public List<User> repairWorker(String word) {
        QueryWrapper wrapper = QueryWrapper.create()
                .from(USER)
                .select(USER.ID, USER.REAL_NAME, USER.PHONE_NUMBER)
                .where(USER.REAL_NAME.like(word, StrUtil.isNotEmpty(word)).and(USER.DEPARTMENT_ID.isNotNull()).or(USER.POSITION_ID.isNotNull()));
        return userMapper.selectListByQuery(wrapper);
    }

    @Override
    public String twins() {
        String res = HttpUtil.get("https://v1.hitokoto.cn/");
        if (StrUtil.isNotEmpty(res) && res.contains("hitokoto")) {
            JSONObject jsonObject = JSONUtil.parseObj(res);
            return jsonObject.getStr("hitokoto");
        }
        return "";
    }

    @Override
    public Map<String, Object> statistics(Long userId) {
        long newToday = this.newToday();
        long waitAssign = repairService.waitAssign();
        long readCount = chatService.noReadCount(userId);
        HashMap<String, Object> res = new HashMap<>();
        res.put("newToday", newToday);
        res.put("waitAssign", waitAssign);
        res.put("readCount", readCount);
        return res;
    }

    @Override
    public long newToday() {
        QueryWrapper nowTodayDate = QueryWrapper.create().from(USER).between("create_time", DateUtil.beginOfDay(new Date()), DateUtil.endOfDay(new Date()));
        return userMapper.selectCountByQuery(nowTodayDate);
    }

    @Override
    public boolean updatePersonalInfo(User user) {
        return TenantManager.withoutTenantCondition(() -> UpdateChain.of(User.class)
                .set(USER.REAL_NAME, user.getRealName())
                .set(USER.PHONE_NUMBER, user.getPhoneNumber())
                .set(USER.EMAIL, user.getEmail())
                .set(USER.PROFILE_PICTURE, user.getProfilePicture())
                .set(USER.EMERGENCY_CONTACT_NAME, user.getEmergencyContactName())
                .set(USER.EMERGENCY_CONTACT_PHONE_NUMBER, user.getEmergencyContactPhoneNumber())
                .set(USER.EDUCATION, user.getEducation())
                .set(USER.GRADUATED_FROM, user.getGraduatedFrom())
                .set(USER.MAJOR, user.getMajor())
                .set(USER.NAME, user.getName())
                .where(USER.ID.eq(user.getId()))
                .update());
    }

    @Override
    public boolean changePwd(JSONObject req) {
        User user = this.getById(req.getLong("userId"));
        if (user != null) {
            if (bCryptPasswordEncoder.matches(req.getStr("oldPwd"), user.getPasswordHash())) {
                return TenantManager.withoutTenantCondition(() -> UpdateChain.of(User.class)
                        .set(USER.PASSWORD_HASH, bCryptPasswordEncoder.encode(req.getStr("newPwd")))
                        .where(USER.ID.eq(req.getLong("userId"))).update()
                );
            }else {
                throw new GlobalException(RespBusiness.WRONG_OLD_PASSWORD);
            }

        }else {
            throw new GlobalException(RespBusiness.NO_USER_EXISTS);
        }
    }

    private static boolean upDateResident(User user) {
        return UpdateChain.of(User.class)
                .set(USER.REAL_NAME, user.getRealName())
                .set(USER.ID_CARD, user.getIdCard())
                .set(USER.TYPE,user.getType())
                .set(USER.PHONE_NUMBER, user.getPhoneNumber())
                .set(USER.SEX, user.getSex())
                .where(USER.ID.eq(user.getId())).update();
    }


}
